/*
 *	dict.c
 *	  part of the xlibrary by SunDEV (http://sundev.890m.com)
 *
 *	Changelog :
 *	  ##-##-## : First public release
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "xcommon.h"

#include "dict.h"

#include "llist.h"
#include "xmalloc.h"	// for xcalloc and xfree
#include "xstring.h"	// for xstrdup

typedef struct {
	char *name;
	unsigned hash;
	void *data;
	u32 dataSize;
} sDict;
#define _SD(llist) ((sDict *)(llist)->data)

LLIST *_curel=NULL;

// private functions prototypes
unsigned _hashkey(char * key);
LLIST *_searchkey(LLIST *el, char *key);
void _deleteentry(sDict *el);

DICT *ld_new() {
// Returns a new list
	return (DICT *)xcalloc(1, sizeof(DICT));
}

bool ld_is_valid(DICT *sl) {
	if(!sl) return false;
	if((!sl->start || !sl->end) && sl->numel) return false;
	if((sl->start && sl->start->prev) || (sl->end && sl->end->next)) return false;

	return true;
}

void ld_set_key_str(DICT *sl, char *key, char *str) {
	ld_set_key_data(sl, key, str, strlen(str)+1);
}

void *ld_set_key_data(DICT *sl, char *key, void *data, u32 dataSize) {
	if(ld_is_valid(sl) && dataSize) {
		LLIST *el=_searchkey(sl->start, key);
		if(el) {
			if(_SD(el)->data) xfree(_SD(el)->data);
		}
		else {
			sl->end=ll_append_el(&sl->end, xmalloc(sizeof(sDict)));
			if(!sl->start) sl->start=sl->end;
			sl->numel++;
			el=sl->end;
			_SD(el)->name=xstrdup(key);
			_SD(el)->hash=_hashkey(key);
		}

		_SD(el)->data=data?xmemdup(data, dataSize):xcalloc(dataSize, 1);
		_SD(el)->dataSize=dataSize;
		return _SD(el)->data;
	}
	return NULL;
}

u32 ld_get_num_el(DICT *sl) {
// Returns the number of elements in the dictionary
	return ld_is_valid(sl)?sl->numel:0;
}

char *ld_get_key_str(DICT *sl, char *key) {
	return (char *)ld_get_key_data(sl, key, NULL);
}

void *ld_get_key_data(DICT *sl, char *key, u32 *dataSize) {
	if(ld_is_valid(sl) && sl->start && key) {
		LLIST *el=_searchkey(sl->start, key);
		if(el) {
			if(dataSize) (*dataSize)=_SD(el)->dataSize;
			return _SD(el)->data;
		}
	}
	return NULL;
}

void *ld_get_first_data(DICT *sl, char **key, u32 *dataSize) {
	if(ld_is_valid(sl) && (_curel=sl->start)) {
		if(key) *key=_SD(_curel)->name;
		if(dataSize) (*dataSize)=_SD(_curel)->dataSize;
		return _SD(_curel)->data;
	}
	return NULL;
}

void *ld_get_next_data(DICT *sl, char **key, u32 *dataSize) {
	if(ld_is_valid(sl) && sl->start && _curel && (_curel=_curel->next)) {
		if(key) *key=_SD(_curel)->name;
		if(dataSize) (*dataSize)=_SD(_curel)->dataSize;
		return _SD(_curel)->data;
	}
	return NULL;
}

void ld_del_key(DICT *sl, char *key) {
	if(ld_is_valid(sl) && sl->start && key) {
		LLIST *el=_searchkey(sl->start, key);
		if(el) {
			if(el==_curel) _curel=_curel->next;
			if(el==sl->start) sl->start=sl->start->next;
			if(el==sl->end) sl->end=sl->end->prev;
			_deleteentry(_SD(el));
			ll_free_el(el);
		}
	}
}

void ld_del(DICT *sl) {
	if(ld_is_valid(sl) && sl->start) {
		_curel=NULL;

		ll_free_all_handler(sl->start, (datahandler)_deleteentry);
		bzero(sl, sizeof(DICT));

		xfree(sl);
	}
}

unsigned _hashkey(char * key) {
	size_t len=strlen(key), i;
	unsigned hash;

	for(hash=0, i=0; i<len; i++) {
		hash+=(unsigned)key[i] ;
		hash+=hash<<10;
		hash^=hash>>6;
	}
	hash+=hash<<3;
	hash^=hash>>11;
	hash+=hash<<15;
	return hash;
}

LLIST *_searchkey(LLIST *el, char *key) {
	unsigned hash;
	if(el) hash=_hashkey(key);
	while(el) {
		if(_SD(el) && _SD(el)->hash==hash && !strcmp(_SD(el)->name, key)) break;
		el=el->next;
	}
	return el;
}

void _deleteentry(sDict *el) {
	if(el->name) xfree(el->name);
	if(el->data) xfree(el->data);
	xfree(el);
}

